package com.bank.manage.config;

import com.bank.manage.constance.BankConstance;
import com.bank.manage.enumerate.BankRedisKey;
import com.bank.manage.enumerate.BankStatus;
import com.bank.manage.exception.BankException;
import com.bank.manage.model.BankCheckRecords;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.gavin.core.attribute.YmlSafetyAttribute;
import org.gavin.core.constant.CommonConst;
import org.gavin.core.enums.CommonStatus;
import org.gavin.core.service.safety.impl.YmlSafety;
import org.gavin.core.utils.GMSM4Utils;
import org.redis.service.RedisService;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * BankSafetyImpl
 *
 * @author grl
 * @date 2024/4/7
 */
@Slf4j
@Component
public class BankSafety extends YmlSafety {

    private final YmlSafetyAttribute ymlSafetyAttribute;
    private final HttpServletRequest request;
    private final RedisService redisService;

    public BankSafety(YmlSafetyAttribute ymlSafety,
                      HttpServletRequest request,
                      RedisService redisService) {
        super(ymlSafety, request);
        this.ymlSafetyAttribute = ymlSafety;
        this.request = request;
        this.redisService = redisService;
    }

    @Override
    public String getEncrypt(String data) {
        String appId = request.getHeader(CommonConst.SAFETY_KEY);
        String encSecret = getMultipleConfig(appId, CommonStatus.CONFIG_ERROR, new BankCheckRecords()).getEncSecret();
        if (StringUtils.isNotBlank(encSecret)) {
            String uniqueValue = request.getHeader(CommonConst.UNIQUE_VALUE);
            String encSecretKey = DigestUtils.md5DigestAsHex((encSecret + uniqueValue).getBytes(StandardCharsets.UTF_8));
            return GMSM4Utils.encryptEcb(data, encSecretKey);
        }
        return data;
    }


    @Override
    public String getDecrypt(String data) {
        BankCheckRecords records = new BankCheckRecords();
        records.setVerificationTime(LocalDateTime.now());
        String appId = request.getHeader(CommonConst.SAFETY_KEY);
        if (StringUtils.isBlank(appId)) {
            records.setKeyVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + CommonStatus.LACK_HEADER_PARAM_ERROR.getMsg() + " : " + CommonConst.SAFETY_KEY);
            throw BankException.le(CommonStatus.LACK_HEADER_PARAM_ERROR, BankConstance.MODEL_NAME, CommonConst.SAFETY_KEY, records);
        }
        records.setSafetyKey(appId);
        String uniqueValue = request.getHeader(CommonConst.UNIQUE_VALUE);
        checkUniqueValue(uniqueValue, records);
        records.setSignatureVerificationResult(BankConstance.SUCCESS);
        String secret = getMultipleConfig(appId, CommonStatus.ACCESS_DENIED, records).getDecSecret();
        records.setKeyVerificationResult(BankConstance.SUCCESS);
        String decSecret = DigestUtils.md5DigestAsHex((secret + uniqueValue).getBytes(StandardCharsets.UTF_8));
        String result = null;
        try {
            result = GMSM4Utils.decryptEcb(data, decSecret);
        } catch (Exception e) {
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + CommonStatus.CIPHERTEXT_HANDLE_FAILED.getMsg());
            records.setRequestJson(uniqueValue + " " + data);
            throw BankException.le(CommonStatus.CIPHERTEXT_HANDLE_FAILED, BankConstance.MODEL_NAME, CommonConst.SAFETY_KEY, records);
        }
        return result;
    }

    private void checkUniqueValue(String uniqueValue, BankCheckRecords records) {
        if (StringUtils.isBlank(uniqueValue) || uniqueValue.length() < BankConstance.UNIQUE_VALUE_LENGTH) {
            records.setSignatureVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + CommonStatus.LACK_HEADER_PARAM_ERROR.getMsg() + " : " + CommonConst.UNIQUE_VALUE + " " + uniqueValue);
            throw BankException.le(CommonStatus.LACK_HEADER_PARAM_ERROR, BankConstance.MODEL_NAME, CommonConst.UNIQUE_VALUE, records);
        }
        long times;
        try {
            times = Long.parseLong(uniqueValue.substring(32));
        } catch (NumberFormatException e) {
            records.setSignatureVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + BankStatus.INVALID_TIMESTAMP.getMsg() + " : " + CommonConst.UNIQUE_VALUE + " " + uniqueValue);
            throw BankException.le(BankStatus.INVALID_TIMESTAMP, BankConstance.MODEL_NAME, CommonConst.UNIQUE_VALUE, records);
        }
        long interval = Math.abs((System.currentTimeMillis() - times) / (1000 * 60));
        long allowInterval = 2L;
        if (allowInterval < interval) {
            records.setSignatureVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + BankStatus.OVER_TIME_SIGNATURE.getMsg() + " : " + CommonConst.UNIQUE_VALUE + " " + uniqueValue + " 超时 " + interval);
            throw BankException.le(BankStatus.OVER_TIME_SIGNATURE, BankConstance.MODEL_NAME, CommonConst.UNIQUE_VALUE, records);
        }
        // uniqueValue 只能使用一次
        BankRedisKey redisKey = BankRedisKey.UNIQUE_VALUE_DEDUPLICATION.setKey(uniqueValue);
        if (!redisService.ifAbsent(redisKey)) {
            records.setSignatureVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + BankStatus.UNIQUE_VALUE_MULTIPLE_USES.getMsg() + " : " + CommonConst.UNIQUE_VALUE + " " + uniqueValue);
            throw BankException.le(BankStatus.UNIQUE_VALUE_MULTIPLE_USES, BankConstance.MODEL_NAME, CommonConst.UNIQUE_VALUE, records);
        }
    }

    private YmlSafetyAttribute.Multiple getMultipleConfig(String appId, CommonStatus status, BankCheckRecords records) {
        Map<String, YmlSafetyAttribute.Multiple> multipleMap = ymlSafetyAttribute.getMultiple();
        if (multipleMap == null || !multipleMap.containsKey(appId)) {
            records.setKeyVerificationResult(BankConstance.FAIL);
            records.setVerificationResult(BankConstance.FAIL);
            records.setOperationResults(BankConstance.OPT_FAIL + " : " + status.getMsg() + CommonConst.SAFETY_KEY + " " + appId + " 不存在");
            throw BankException.le(status, BankConstance.MODEL_NAME, appId, records);
        }
        return multipleMap.get(appId);
    }
}
